package edu.polsl.bio.str.manager.logic;

import edu.polsl.bio.str.common.constant.NodeState;
import edu.polsl.bio.str.common.constant.Strand;
import edu.polsl.bio.str.common.dto.StrMatch;
import edu.polsl.bio.str.common.entity.*;
import edu.polsl.bio.str.common.exception.ManagerException;
import edu.polsl.bio.str.common.messaging.StrDotsPayload;
import edu.polsl.bio.str.common.util.SerializationUtils;
import edu.polsl.bio.str.manager.dao.*;
import edu.polsl.bio.str.manager.event.DotPlotResultsEvent;
import edu.polsl.bio.str.manager.messaging.ManagerMessagePublisher;
import edu.polsl.bio.str.manager.vo.DotPlotResult;
import edu.polsl.bio.tools.str.StrRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.LinkedList;
import java.util.List;

/**
 * //todo class description
 * <p/>
 * Created on: Jun 17, 2011 1:59:35 PM <br/>
 * <a href="http://www.polsl.pl">www.polsl.pl</a>
 *
 * @author lukasz.olczak@polsl.pl
 */
public class DotPlotBeanImpl implements DotPlotBean {
  /**
   * slf4j Logger.
   */
  private static final Logger log = LoggerFactory.getLogger(DotPlotBeanImpl.class.getName());

  private static int MAX_RESULTS = 100000;

  private DotPlotDao dotPlotDao;

  private ChromInfoDao chromInfoDao;

  private JobDao jobDao;

  private JobGroupDao jobGroupDao;

  private NodeDao nodeDao;

  private DotImageDao dotImageDao;

  private ManagerMessagePublisher managerEventPublisher;

  private String imgDirPath;

  private File imgDir;

  @PostConstruct
  public void init() {
    imgDir = new File(imgDirPath);
    if (!imgDir.exists()) {
      log.debug("Making dir {}", imgDirPath);
      imgDir.mkdirs();
    }
  }


  @Override
  @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
  public void handleResults(StrDotsPayload results) throws Exception {
    Node node = nodeDao.getById(results.getNodeId());
    if (!node.getNodeState().equals(NodeState.ATTACHED)) {
      throw new ManagerException("Node " + results.getNodeId() + " is not attached");
    }
    managerEventPublisher.publishEvent(new DotPlotResultsEvent(results.getJobId(), results.getNodeId(), results));
  }

  @Override
  @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED)
  public void persistDotPlot(StrDotsPayload payload) throws Exception {
    log.debug("Persisting dot plot results {}", payload);
    Job job = jobDao.getById(payload.getJobId());
    JobGroup jobGroup = jobGroupDao.getById(job.getJobGroupId());

    List<DotPlot> rows = new LinkedList();
    for (StrMatch match : payload.getDots()) {
      DotPlot dotPlot = newDotPlot(match, jobGroup.getRefId(), jobGroup.getQueryId(), jobGroup.getRefChromosomeId(),
                                   jobGroup.getQueryChromosomeId(), Strand.FORWARD);
      rows.add(dotPlot);
    }
    for (StrMatch match : payload.getReverseDots()) {
      DotPlot dotPlot = newDotPlot(match, jobGroup.getRefId(), jobGroup.getQueryId(), jobGroup.getRefChromosomeId(),
                                   jobGroup.getQueryChromosomeId(), Strand.BACKWARD);
      rows.add(dotPlot);
    }
    dotPlotDao.bulkInsert(rows);
  }

  private DotPlot newDotPlot(StrMatch strMatch, Long refIndividualId, Long queryIndividualId, Long refChromosomeId,
                             Long queryChromosomeId, Strand strand) {
    DotPlot dotPlot = new DotPlot();

    StrRecord ref = strMatch.getRefStr();

    dotPlot.setMotif(ref.getMotif());
    dotPlot.setMotifLength(ref.getMotifLength());

    dotPlot.setRefStrId(ref.getId());
    dotPlot.setRefStart(ref.getStartIndex());
    dotPlot.setRefEnd(ref.getEndIndex());
    dotPlot.setRefNoRepeats(ref.getNoRepeats());

    StrRecord query = strMatch.getQueryStr();
    dotPlot.setQueryStrId(query.getId());
    dotPlot.setQueryStart(query.getStartIndex());
    dotPlot.setQueryEnd(query.getEndIndex());
    dotPlot.setQueryNoRepeats(query.getNoRepeats());

    dotPlot.setRefId(refIndividualId);
    dotPlot.setQueryId(queryIndividualId);
    dotPlot.setRefChromosomeId(refChromosomeId);
    dotPlot.setQueryChromosomeId(queryChromosomeId);

    dotPlot.setQueryStrand(strand);

    return dotPlot;
  }

  @Override
  public void generateDotPlot(Long refIndividualId, Long queryIndividualId, Long refChromosomeId,
                              Long queryChromosomeId, int width) throws Exception {
    log.debug("Generating dot plot for refId={}, queryId={}, refChromId={}, queryChromId={}, width={}",
              new Object[]{refIndividualId, queryIndividualId, refChromosomeId, queryChromosomeId, width});

    int maxRefIndex = chromInfoDao.getSizeFor(refIndividualId, refChromosomeId);
    int maxQueryIndex = chromInfoDao.getSizeFor(queryIndividualId, queryChromosomeId);
    log.debug("MaxQueryIndex={}, MaxRefIndex={}", maxQueryIndex, maxRefIndex);

    List<DotPlot> dots;
    int height = (int) Math.floor(((double) maxQueryIndex * (double) width) / ((double) maxRefIndex));
    log.debug("Height for img {}", height);
    double[][] dotPlot = new double[width][height];
    double[][] backwardDotPlot = new double[width][height];

    int offset = 0;
    while ((dots = dotPlotDao.getDotPlots(refIndividualId, queryIndividualId, refChromosomeId,
                                          queryChromosomeId, offset, MAX_RESULTS)).size() > 0) {
      log.debug("Fetched {} rows for dot plot", dots.size());
      offset += MAX_RESULTS;

      for (DotPlot dot : dots) {
        int qIndex = getBucketNumber(dot.getQueryStart(), maxQueryIndex, height);
        int rIndex = getBucketNumber(dot.getRefStart(), maxRefIndex, width);
        if (dot.getQueryStrand().equals(Strand.FORWARD)) {
          dotPlot[rIndex][qIndex]++;
        } else {
          backwardDotPlot[rIndex][qIndex]++;
        }
      }
    }

    String filePath = saveDotData(new DotPlotResult(dotPlot, backwardDotPlot));
    DotImage dotImage = new DotImage();
    dotImage.setFilePath(filePath);
    dotImage.setWidth(width);
    dotImage.setRefId(refIndividualId);
    dotImage.setRefChromosomeId(refChromosomeId);
    dotImage.setQueryId(queryIndividualId);
    dotImage.setQueryChromosomeId(queryChromosomeId);
    log.debug("Persisting {}", dotImage);
    dotImageDao.create(dotImage);
  }

  @Override
  @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
  public DotPlotResult getDotPlot(Long refIndividualId, Long queryIndividualId, Long refChromosomeId,
                                  Long queryChromosomeId, int width) throws Exception {
    log.debug("Getting dot plot for refId={}, queryId={}, refChromId={}, queryChromId={}, width={}",
              new Object[]{refIndividualId, queryIndividualId, refChromosomeId, queryChromosomeId, width});

    DotImage dotImage = dotImageDao.get(refIndividualId, queryIndividualId, refChromosomeId, queryChromosomeId, width);
    byte[] data = SerializationUtils.readAsByteArray(new File(dotImage.getFilePath()));
    DotPlotResult result = (DotPlotResult) SerializationUtils.decompressObject(data);
    return result;
  }

  private String saveDotData(DotPlotResult dotImg) {
    try {
      File outFile = File.createTempFile("dotImg_", ".bin", imgDir);
      SerializationUtils.saveAndCompressObject(dotImg, outFile);
      return outFile.getAbsolutePath();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private int getBucketNumber(int index, int maxIndex, int noBuckets) {
    double bucketSize = (double) maxIndex / (double) noBuckets;

    return (int) Math.floor((double) index / bucketSize);
  }

  public void setDotPlotDao(DotPlotDao dotPlotDao) {
    this.dotPlotDao = dotPlotDao;
  }

  public void setJobDao(JobDao jobDao) {
    this.jobDao = jobDao;
  }

  public void setJobGroupDao(JobGroupDao jobGroupDao) {
    this.jobGroupDao = jobGroupDao;
  }

  public void setNodeDao(NodeDao nodeDao) {
    this.nodeDao = nodeDao;
  }

  public void setManagerEventPublisher(ManagerMessagePublisher managerEventPublisher) {
    this.managerEventPublisher = managerEventPublisher;
  }

  public void setChromInfoDao(ChromInfoDao chromInfoDao) {
    this.chromInfoDao = chromInfoDao;
  }

  public void setDotImageDao(DotImageDao dotImageDao) {
    this.dotImageDao = dotImageDao;
  }

  public void setImgDirPath(String imgDirPath) {
    this.imgDirPath = imgDirPath;
  }
}
